package com.misyi.framework.web.okhttp;

import com.misyi.framework.api.SystemCodeEnum;
import com.misyi.framework.core.util.JSONUtils;
import com.misyi.framework.web.exception.BusinessException;
import okhttp3.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.util.Assert;

import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.GZIPInputStream;

/**
 * OkHttp请求工具类
 *
 * @author wuwen
 * @date 2018-03-26 9:56 AM
 */
public class OkHttpRequestFactory {

    private final static Logger LOGGER = LogManager.getLogger(OkHttpRequestFactory.class);

    private final OkHttpClient okHttpClient;

    private OkHttpRequestFactory() {
        this.okHttpClient = OkHttpClientFactory.getOkHttpClient();
    }

    private OkHttpRequestFactory(OkHttpClient okHttpClient) {
        this.okHttpClient = okHttpClient;
    }

    public static OkHttpRequestFactory getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final OkHttpRequestFactory INSTANCE = new OkHttpRequestFactory();
    }

    public static OkHttpRequestFactory getInstance(OkHttpClient okHttpClient) {
        Assert.notNull(okHttpClient, "OkHttpClient must not be null");
        return new OkHttpRequestFactory(okHttpClient);
    }

    /**
     * 发送GET请求
     *
     * @param url 请求url
     * @return 响应数据
     */
    public Response doGet(String url) {
        Assert.hasLength(url, "请求url不能为空");
        return doGet(null, url);
    }

    /**
     * 发送GET请求
     *
     * @param url           请求url
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据转换类泛型
     * @return 响应数据
     */
    public <T> T doGet(String url, Class<T> responseClazz) {
        Assert.hasLength(url, "请求url不能为空");
        Assert.notNull(responseClazz, "响应类不能为空");
        return doGet(null, url, responseClazz);
    }

    /**
     * 发送GET请求
     *
     * @param url           请求url
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据转换类泛型
     * @return 响应数据
     */
    public <T> T doGet(Map<String, String> headers, String url, Class<T> responseClazz) {
        Response response = doGet(headers, url);
        try {
            return processResponse(responseClazz, response);
        } finally {
            close(response);
        }
    }

    /**
     * 发送GET请求
     *
     * @param url 请求url
     * @return 响应数据
     */
    public Response doGet(Map<String, String> headers, String url) {
        Assert.hasLength(url, "请求url不能为空");
        try {
            LOGGER.debug("GET请求的headers:{}, url:{}", headers, url);
            Request.Builder builder = new Request.Builder().url(url);
            Call call = okHttpClient.newCall(getRequest(headers, builder));
            return call.execute();
        } catch (Exception ex) {
            LOGGER.error("GET请求url：{}出现异常：{}", url, ex.getMessage());
            throw new RuntimeException(ex);
        }
    }

    /**
     * 支持Gzip压缩的Get请求
     *
     * @param headers 请求头
     * @param url     请求url
     * @return 响应数据
     */
    public String doGetSupportGzip(Map<String, String> headers, String url) {
        Response response = null;
        String result;
        try {
            response = doGet(headers, url);
            String contentEncoding = response.headers().get("Content-Encoding");
            if (contentEncoding != null && contentEncoding.contains("gzip")) {
                result = unCompress(response.body().byteStream());
            } else {
                result = getResponseBody(response);
            }
        } catch (Exception e) {
            LOGGER.error("GET请求url：{}出现异常：{}", url, e.getMessage());
            throw new RuntimeException(e);
        } finally {
            close(response);
        }
        return result;
    }

    /**
     *
     * @param url         请求url
     * @param requestBody 响应数据转换类
     * @return 响应数据
     */
    public Response doPost(String url, RequestBody requestBody) {
        return doPost(null, url, requestBody);
    }

    /**
     * 发送POST请求
     *
     * @param url           请求url
     * @param requestBody   请求内容
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doPost(String url, RequestBody requestBody, Class<T> responseClazz) {
        return doPost(null, url, requestBody, responseClazz);
    }

    /**
     * 发送POST请求
     *
     * @param url           请求url
     * @param body          请求内容
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doPost(String url, String body, Class<T> responseClazz) {
        return doPost(null, url, body, responseClazz);
    }

    /**
     * 发送POST请求
     *
     * @param url           请求url
     * @param body          请求内容
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doPost(Map<String, String> headers, String url, String body, Class<T> responseClazz) {
        RequestBody requestBody = RequestBody.create(MediaType.parse(org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE), body);
        return doPost(headers, url, requestBody, responseClazz);
    }

    /**
     * 发送POST请求
     *
     * @param url           请求url
     * @param requestBody   请求内容
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doPost(Map<String, String> headers, String url, RequestBody requestBody, Class<T> responseClazz) {
        Response response = null;
        try {
            response = doPost(headers, url, requestBody);
            return processResponse(responseClazz, response);
        } finally {
            close(response);
        }
    }

    /**
     * 发送POST请求
     *
     * @param url         请求url
     * @param requestBody 请求内容
     * @return 响应数据
     */
    public Response doPost(Map<String, String> headers, String url, RequestBody requestBody) {
        Assert.hasLength(url, "请求url不能为空");
        Assert.notNull(requestBody, "请求体不能为空");
        try {
            LOGGER.info("POST请求的headers：{}, url：{},参数：{}", headers, url, JSONUtils.toJSONString(requestBody));
            Request.Builder builder = new Request.Builder().url(url).post(requestBody);
            Call call = okHttpClient.newCall(getRequest(headers, builder));
            return call.execute();
        } catch (Exception ex) {
            LOGGER.error("POST请求url：{}出现异常：{}", url, ex.getMessage());
            throw new RuntimeException(ex);
        }
    }

    /**
     * 发送FORM请求
     *
     * @param url           请求url
     * @param params        请求参数
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doForm(String url, Map<String, String> params, Class<T> responseClazz) {
        return doForm(null, url, params, responseClazz);
    }

    /**
     * 发送FORM请求
     *
     * @param url           请求url
     * @param params        请求参数
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doForm(Map<String, String> headers, String url, Map<String, String> params, Class<T> responseClazz) {
        Response response = null;
        try {
            response = doForm(headers, url, params);
            return processResponse(responseClazz, response);
        } finally {
            close(response);
        }
    }

    /**
     * 发送FORM请求，处理在200和非200情况下都解析响应体
     *
     * @param url           请求url
     * @param params        请求参数
     * @param responseClazz 响应数据转换类
     * @param <T>           响应数据泛型
     * @return 响应数据
     */
    public <T> T doFormOnProcessAll(Map<String, String> headers, String url, Map<String, String> params,
                                    Class<T> responseClazz) {
        Response response = null;
        try {
            response = doForm(headers, url, params);
            return parseResponseBody(responseClazz, response);
        } finally {
            close(response);
        }
    }


    /**
     * 发送FORM请求
     *
     * @param url    请求url
     * @param params 请求参数
     * @return 响应数据
     */
    public Response doForm(Map<String, String> headers, String url, Map<String, String> params) {
        return doForm(headers, url, params, false);
    }

    /**
     * 发送FORM请求
     *
     * @param url    请求url
     * @param params 请求参数
     * @return 响应数据
     */
    public Response doForm(Map<String, String> headers, String url, Map<String, String> params, boolean urlEncoded) {
        FormBody.Builder builder = new FormBody.Builder();
        if (null != params && params.size() > 0) {
            params.entrySet().stream()
                    .filter(entry -> StringUtils.isNotBlank(entry.getValue()))
                    .forEach(entry -> {
                        if (urlEncoded) {
                            builder.addEncoded(entry.getKey(), entry.getValue());
                        } else {
                            builder.add(entry.getKey(), entry.getValue());
                        }
                    });
        }
        return doForm(headers, url, builder.build());
    }

    /**
     * 发送FORM请求
     *
     * @param url      请求url
     * @param formBody 请求参数
     * @return 响应数据
     */
    public Response doForm(Map<String, String> headers, String url, FormBody formBody) {
        Assert.hasLength(url, "请求url不能为空");
        Assert.notNull(formBody, "表单体参数不能为空");
        try {
            LOGGER.info("FORM请求的url:{},参数：{}", url, JSONUtils.toJSONString(formBody));
            Request.Builder builder = new Request.Builder().url(url).post(formBody);
            Call call = okHttpClient.newCall(getRequest(headers, builder));
            return call.execute();
        } catch (Exception ex) {
            LOGGER.error("FORM请求的url：{}出现异常：{}", url, ex.getMessage());
            throw new RuntimeException(ex);
        }
    }

    /**
     * 下载图片
     *
     * @param url      请求url
     * @param basePath 文件根据路径
     * @param dirPath  文件目录路径
     * @return 响应数据
     */
    public String downloadImage(String url, String basePath, String dirPath) {
        Assert.hasLength(basePath, "文件根据路径不能为空");
        Assert.hasLength(dirPath, "文件目录路径不能为空");
        Response response = doGet(url);
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        try {
            String canonicalPath = getCanonicalPath(basePath, dirPath);
            String imageName = getImageName(response.header("Content-disposition"));
            String filePath = canonicalPath + imageName;
            LOGGER.debug("服务器图片存储路径：{}", filePath);
            fos = new FileOutputStream(new File(filePath));
            bos = new BufferedOutputStream(fos);
            IOUtils.copy(response.body().byteStream(), bos);
            return dirPath + File.separator + imageName;
        } catch (Exception ex) {
            LOGGER.error("GET请求下载图片出现异常：{}", ex.getMessage());
            throw new RuntimeException(ex);
        } finally {
            IOUtils.closeQuietly(bos);
            IOUtils.closeQuietly(fos);
            close(response);
        }
    }

    private static final String MEDIA_TYPE_IMAGE = "image/*";

    /**
     * 增加图片处理请求
     *
     * @param url            请求url
     * @param fileParameters key为文件参数名，value为文件绝对路径
     * @param formParameters 文件
     * @param responseClazz  响应体转换类
     * @param <T>            响应体泛型
     * @return 响应数据
     */
    public <T> T doMultipartImage(String url,
                                  Map<String, File> fileParameters,
                                  Map<String, String> formParameters,
                                  Class<T> responseClazz) {
        return doMultipart(url, MEDIA_TYPE_IMAGE, fileParameters, formParameters, responseClazz);
    }

    /**
     * 增加文件处理请求
     *
     * @param url            请求url
     * @param mediaType      文件类型
     * @param fileParameters key为文件参数名，value为文件绝对路径
     * @param formParameters 表单数据
     * @param responseClazz  响应体转换类
     * @param <T>            响应体泛型
     * @return 响应数据
     */
    public <T> T doMultipart(String url, String mediaType,
                             Map<String, File> fileParameters,
                             Map<String, String> formParameters,
                             Class<T> responseClazz) {
        return doMultipart(url, mediaType, null, fileParameters, formParameters, responseClazz);
    }

    /**
     * 增加文件处理请求
     *
     * @param url            请求路径
     * @param mediaType      请求类型
     * @param headers        请求headers
     * @param fileParameters key为文件参数名，value为文件绝对路径
     * @param formParameters 表单参数
     * @param responseClazz  响应结构体类型
     * @return 响应数据
     */
    public <T> T doMultipart(String url, String mediaType,
                             Map<String, String> headers,
                             Map<String, File> fileParameters,
                             Map<String, String> formParameters,
                             Class<T> responseClazz) {
        Assert.hasLength(url, "请求url不能为空");
        Assert.hasLength(mediaType, "mediaType不能为空");
        Assert.notEmpty(fileParameters, "文件参数不能为空");
        Assert.notNull(responseClazz, "响应类不能为空");
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        fileParameters.entrySet().stream().forEach(entry -> {
            File file = entry.getValue();
            RequestBody body = RequestBody.create(MediaType.parse(mediaType), file);
            builder.addFormDataPart(entry.getKey(), file.getName(), body);
        });
        if (null != formParameters && formParameters.size() > 0) {
            formParameters.entrySet().stream()
                    .forEach(entry -> builder.addFormDataPart(entry.getKey(), entry.getValue()));
        }
        return doPost(headers, url, builder.build(), responseClazz);
    }

    /**
     * 文件批量处理请求
     *
     * @param url            请求路径
     * @param mediaType      请求类型
     * @param fileParameters key为文件参数名，value为文件绝对路径
     * @param formParameters 表单参数
     * @param responseClazz  响应结构体类型
     * @return 响应数据
     */
    public <T> T doBatchMultipart(String url, String mediaType,
                                  Map<String, List<File>> fileParameters,
                                  Map<String, String> formParameters,
                                  Class<T> responseClazz) {
        return doBatchMultipart(url, mediaType, null, fileParameters, formParameters, responseClazz);
    }

    /**
     * 文件批量处理请求
     *
     * @param url            请求路径
     * @param mediaType      请求类型
     * @param headers        请求headers
     * @param fileParameters key为文件参数名，value为文件绝对路径
     * @param formParameters 表单参数
     * @param responseClazz  响应结构体类型
     * @return 响应数据
     */
    public <T> T doBatchMultipart(String url, String mediaType,
                             Map<String, String> headers,
                             Map<String, List<File>> fileParameters,
                             Map<String, String> formParameters,
                             Class<T> responseClazz) {
        Assert.hasLength(url, "请求url不能为空");
        Assert.hasLength(mediaType, "mediaType不能为空");
        Assert.notEmpty(fileParameters, "文件参数不能为空");
        Assert.notNull(responseClazz, "响应类不能为空");
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        for (Map.Entry<String, List<File>> value : fileParameters.entrySet()) {
            for (File file : value.getValue()) {
                RequestBody body = RequestBody.create(MediaType.parse(mediaType), file);
                builder.addFormDataPart(value.getKey(), file.getName(), body);
            }
        }
        if (null != formParameters && formParameters.size() > 0) {
            formParameters.entrySet().stream()
                    .forEach(entry -> builder.addFormDataPart(entry.getKey(), entry.getValue()));
        }
        return doPost(headers, url, builder.build(), responseClazz);
    }

    /**
     * 上传图片到服务器
     *
     * @param url              请求url
     * @param absoluteFilePath 文件绝对路径
     * @param responseClazz    响应体转换类
     * @param <T>              响应体转换泛型
     * @return 响应数据
     */
    public <T> T uploadImage(String url, String absoluteFilePath, Class<T> responseClazz) {
        RequestBody requestBody = RequestBody.create(MediaType.parse(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE), new File(absoluteFilePath));
        return doPost(url, requestBody, responseClazz);
    }

    private static final String ERROR_KEY = "error";

    public static <T> T processResponse(Class<T> responseClazz, Response response) {
        return processResponse(responseClazz, response, ERROR_KEY);
    }

    public static <T> T processResponse(Class<T> responseClazz, Response response, String errorKey) {
        String responseBody = getResponseBody(response);
        LOGGER.info("OkHttp响应结果：{}", responseBody);
        if (response.isSuccessful()) {
            return parseResponseBody(responseBody, responseClazz);
        }
        String errorMessage = String.format("OkHttp响应结果:%s--%s", response.code(), response.message());
        String error = JSONUtils.getStringValue(responseBody, errorKey);
        if (StringUtils.isNotBlank(error)) {
            errorMessage = errorMessage.concat("，错误消息：").concat(error);
        }
        throw new BusinessException(SystemCodeEnum.HTTP_REQUEST_FAILURE.getValue(),
                SystemCodeEnum.HTTP_REQUEST_FAILURE.getDesc(), errorMessage);
    }

    public static <T> T parseResponseBody(Class<T> responseClazz, Response response) {
        String responseBody = getResponseBody(response);
        return parseResponseBody(responseBody, responseClazz);
    }

    public static <T> T parseResponseBody(String responseBody, Class<T> responseClazz) {
        Assert.notNull(responseClazz, "响应类不能为空");
        if (responseClazz.getName().equals("java.lang.String")) {
            return (T) responseBody;
        } else {
            return JSONUtils.parseObject(responseBody, responseClazz);
        }
    }

    public static String getResponseBody(Response response) {
        Assert.notNull(response, "响应结果不能为空");
        try {
            return response.body().string();
        } catch (IOException e) {
            LOGGER.error("获取响应体异常：{}", ExceptionUtils.getMessage(e));
            throw new RuntimeException(e);
        }
    }

    private static Request getRequest(Map<String, String> headers, Request.Builder builder) {
        if (null != headers && headers.size() > 0) {
            headers.entrySet().stream()
                    .filter(entry -> StringUtils.isNotBlank(entry.getValue()))
                    .forEach(entry -> builder.addHeader(entry.getKey(), entry.getValue()));
        }

        return builder.build();
    }

    private String getCanonicalPath(String basePath, String dirPath) {
        if (StringUtils.isBlank(dirPath)) {
            dirPath = "temp/";
        }
        String canonicalPath = basePath + dirPath + File.separator;
        File fileDirectory = new File(canonicalPath);
        if (!fileDirectory.exists() || !fileDirectory.isDirectory()) {
            fileDirectory.mkdirs();
        }
        return canonicalPath;
    }

    private String getImageName(String disposition) {
        String fileName;
        if (StringUtils.isNotBlank(disposition)) {
            fileName = disposition.substring(disposition.indexOf("=") + 2, disposition.length() - 1);
        } else {
            fileName = UUID.randomUUID().toString().concat(".jpg");
        }
        return fileName;
    }

    public void close(Response response) {
        if (null != response) {
            response.close();
        }
    }

    private static String unCompress(InputStream inputStream) throws IOException {
        StringWriter writer = null;
        InputStream decompressedResponse = null;
        Reader reader = null;
        try {
            decompressedResponse = new GZIPInputStream(inputStream);
            reader = new InputStreamReader(decompressedResponse, "UTF-8");
            writer = new StringWriter();

            char[] buffer = new char[10240];
            for (int length = 0; (length = reader.read(buffer)) > 0; ) {
                writer.write(buffer, 0, length);
            }
            return writer.toString();
        } catch (Exception e) {
            String error = String.format("GZIP报文解压缩异常，异常信息：", e.getMessage());
            LOGGER.error(error, e);
            throw new BusinessException(SystemCodeEnum.SYSTEM_EXCEPTION.getValue(), error);
        } finally {
            if (writer != null) {
                writer.close();
            }
            if (reader != null) {
                reader.close();
            }
            if (decompressedResponse != null) {
                decompressedResponse.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

}
